#include <cstdio>
#include <cstdlib>
//#include <tchar.h>

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/video/video.hpp"
#include <opencv2/gpu/gpu.hpp>


#include "VideoRead.h"

#include "FeatureComputer.h"

#include "Classifier.h"

#include "Bagging.h"

#include "InterFace.h"

void getLabelMask( Mat & res, Mat & mask , Size size, int bound_setting);

LcClassifier * loadBagging(	LcInterface & my_setting,
							string * p_video_name = NULL);

void loadCrank(		LcInterface & my_setting,
					string name_str,
					vector< LcClassifier* > & baggings,
					string * p_video_name = NULL);

void getCorr( Mat & res1,
			  Mat & res2,
			  Mat & gt,
			  float & corr_pos,
			  float & corr_neg)
{
	int sz = gt.rows * gt.cols;

	float * p_gt = gt.ptr< float >();
	float * p1 = res1.ptr< float >();
	float * p2 = res2.ptr< float >();

	float m1_pos = 0 , m2_pos = 0, m1_neg = 0, m2_neg = 0;
	corr_pos = 0;
	corr_neg = 0;
	float n_pos = 1e-3f, n_neg = 1e-3f;

	for( ; sz>0; sz--, p1++, p2++, p_gt++)
	{
		if(*p_gt>.5f)
		{
			n_pos += 1.0f;
			m1_pos += *p1;
			m2_pos += *p2;
			corr_pos += (*p1) * (*p2);
		}
		else
		{
			n_neg += 1.0f;
			m1_neg += *p1;
			m2_neg += *p2;
			corr_neg += (*p1) * (*p2);
		}
	}

	m1_pos /= n_pos;
	m2_pos /= n_pos;
	corr_pos /= n_pos;
	corr_pos -= ( m1_pos * m2_pos );

	m1_neg /= n_neg;
	m2_neg /= n_neg;
	corr_neg /= n_neg;
	corr_neg -= ( m1_neg * m2_neg );
}


int main(int argc, char * const argv[])
{

	LcInterface my_setting;

	if(argc==1)
	{
		char *const setting_string[] ={ "","-b","-v","l","-t","GOPR2384_cab","-s", "550", "-e", "700", "-r","50","-p","GOPR2382_cab","-f","./../../_FGS/","-m","ADL"};
		my_setting.input( 17 + 1 ,setting_string);
	}
	else my_setting.input( argc, argv);

	if(my_setting.IF_TRAIN)
	{
		//extractor global feature
		vector< LcGlobalExtractor * > extractors;

		int extractor_n = 10; extractors.reserve( extractor_n ); extractors.clear();

		for(int i = 0 ; i < extractor_n; i++)
		{
			switch( i/5)
			{
				case 0:
					extractors.push_back( new LcSpatialHistogram( i%5) );
					break;
				case 1:
					extractors.push_back( new LcSpatialHoG( i%5) );
					break;
			}			
		}

		LcVideoRead * test_reader = my_setting.getTrainVideop();

		vector< Mat > feats; feats.resize( extractor_n );

		int frame_n = test_reader->getN();

		for(int j = 0 ; j < extractor_n ; j++)
			feats[j] = Mat::zeros( frame_n, extractors[j]->dim, 5 );

		for(int i = 0 ; i < 1e5;i++)
		{

			Mat frm;

			int status = test_reader->readNext( frm );

			if(status) break;

			cout << "========= work with frame " << test_reader->getf() << " =======" << endl;

			for(int j = 0 ; j < extractor_n ; j++)
			{
				Mat temp;
				extractors[j]->work( frm , temp );
				temp.copyTo( feats[j].row(i) );
			}

		}

		for(int j = 0 ; j < extractor_n ; j++)
		{
			stringstream ss; ss << j;

			string feature_save_name;
			feature_save_name = test_reader->root + "recommendation_output/" 
				+ test_reader->video_name + "_global_feat_"+ ss.str()  + ".bin";

			lc::LcMat2Bin( feature_save_name.c_str() ,feats[j]);
		}
	}

	
	if(my_setting.IF_TEST)
	{

		vector< LcClassifier* > classifier_pool; classifier_pool.clear();

		if(my_setting.data_set_name == "")
		{//defualt load a bagging
			LcClassifier * my_bagging = loadBagging( my_setting );
			classifier_pool.push_back( my_bagging );
		}
		else
		{
			char bag_file_name[400];
			sprintf(	bag_file_name ,"bag_name_%s.txt", 
						my_setting.data_set_name.c_str());

			FILE * fin = fopen( bag_file_name , "r" );

			int n;
			fscanf( fin, "%d", &n);

			for(int i =0;i< n ; i++)
			{
				char video_name[400];
				fscanf( fin, "%s", video_name );
				string str_video_name = string( video_name );
				cout << str_video_name << endl;
				LcClassifier * my_bagging = loadBagging(	my_setting,
															&str_video_name);

				classifier_pool.push_back( my_bagging );
			}

			fclose(fin);			
		}

		{
			char crank_file_name[400];

			if(my_setting.data_set_name == "")
				sprintf(crank_file_name,"crank_name.txt");
			else
			{
				sprintf(	crank_file_name ,"crank_name_%s.txt", 
							my_setting.data_set_name.c_str());
			}

			FILE * fin = fopen(crank_file_name ,"r");

			int n;

			fscanf( fin, "%d", &n);		

			classifier_pool.reserve( n * 25 + 300 );

			for(int i = 0 ; i < n; i++)
			{
				char crank_name[400]; fscanf(fin,"%s",crank_name);
				string name_str = crank_name;
				vector< LcClassifier* > temp_baggings;

				if(my_setting.data_set_name == "")
					loadCrank( my_setting, crank_name , temp_baggings );
				else
				{
					char video_name[400];
					fscanf( fin, "%s", video_name );
					string str_video_name = string( video_name );
					loadCrank( my_setting, crank_name , temp_baggings ,&str_video_name);
				}

				for(int j = 0 ; j < temp_baggings.size();j++)
					classifier_pool.push_back( temp_baggings[j] );
					
			}

			fclose(fin);
		}

		cout << classifier_pool.size() << " classifiers load in " << endl;		

		LcVideoRead * test_reader = my_setting.getTestVideop();

		LcFeatureExtractor my_extractor;

		my_extractor.set_extractor( my_setting.feature_code_string.c_str() );

		int classifier_n = (int) classifier_pool.size();

		for(int i = 0 ; i < classifier_n-1;i++)
			for(int j = i+1; j < classifier_n ; j++)
			{
				if(classifier_pool[i] == classifier_pool[j])
				{
					cout << " warning! memory ovelapping!" << endl;
					exit(1);
				}
			}
		

		int frame_n = test_reader->getN();

		Mat score_record = Mat::zeros(frame_n, classifier_n * 5 , 5 );

		Mat corr_pos = Mat::zeros( classifier_n,classifier_n,5);
		Mat corr_neg = Mat::zeros( classifier_n,classifier_n,5);

		double avg_time = -1.0;

		string prefix_str = "";

		if(my_setting.data_set_name != "")
			prefix_str = my_setting.data_set_name + "_";

		cout << "prefix is " << prefix_str << endl;		
		
		test_reader->reset();

		for(int i = 0 ; i < 1e5;i++)
		{
			Mat frm,gt;

			double t = getTickCount();
		
		

			int status = test_reader->readNext( frm );

			if(status) break;

			cout << "========= work with frame " << test_reader->getf() << " =======" << endl;

			test_reader->getLabel( gt );		

			cv::resize( gt, gt, frm.size() );

			//if(gt.type() == CV_8UC3 ) cv::cvtColor( gt, gt, CV_RGB2GRAY);
			//if(gt.type() == CV_8U){ gt.convertTo( gt, 5); gt = gt * (1.0/255.0); }

			

			Mat desc, lab;

			my_extractor.work( frm, desc, gt, lab);

			vector< Mat > ress; ress.resize( classifier_n );

			vector<float> scores; scores.reserve( classifier_n);
			vector<int> sort_id;

			for(int j = 0, score_record_j = 0 ; j < classifier_n ; j++ )
			{
				Mat & res = ress[j];
				LcValidator temp_vali = classifier_pool[j]->predict( desc, res, lab );

				temp_vali = LcValidator( res, lab );			

				scores.push_back( temp_vali.getF1() );

				score_record.at<float>(i,score_record_j) = temp_vali.getF1(); score_record_j++;
				score_record.at<float>(i,score_record_j) = temp_vali.tp; score_record_j++;
				score_record.at<float>(i,score_record_j) = temp_vali.fp; score_record_j++;
				score_record.at<float>(i,score_record_j) = temp_vali.fn; score_record_j++;
				score_record.at<float>(i,score_record_j) = temp_vali.tn; score_record_j++;
			}

			lc::argsort( scores, sort_id);

			cout << "F: " << scores[sort_id[0]] << " - " << scores[sort_id[classifier_n/2]]
				<< " - " << scores[sort_id[classifier_n-1]] << endl;			

			for(int j = 0 ; j < classifier_n; j++)
			{
				Mat mask;
				getLabelMask( ress[j], mask, frm.size(), my_extractor.bound_setting );			

				
				stringstream ss; ss << i << "_c" << j;

				string res_name = test_reader->root + "res_pool/" 
					+ prefix_str + test_reader->video_name + "_" + my_setting.feature_code_string + "_" 
					+ ss.str()+ ".jpg";				

				vector<int> params;

				params.push_back( CV_IMWRITE_JPEG_QUALITY );
				params.push_back( 100 );

				imwrite( res_name , mask, params );
			}

			if(rand()%10000==1247)
			{
				for(int j = 0 ; j < classifier_n ; j++ )
					for(int k = j ; k < classifier_n ; k++)
					{
						float _corr_pos, _corr_neg;

						getCorr( ress[j], ress[k], lab , _corr_pos, _corr_neg);

						corr_pos.at<float>(j,k) +=  _corr_pos;
						corr_pos.at<float>(k,j) +=  _corr_pos;
						
						corr_neg.at<float>(j,k) +=  _corr_neg;
						corr_neg.at<float>(k,j) +=  _corr_neg;
					}
			}
			
			for(int j = 0 ; j < classifier_n ; j++ ) ress[j].release();

			t = (getTickCount()-t)/getTickFrequency();

			if(avg_time<0) avg_time = t; else avg_time = avg_time*0.9 + t*0.1;

			cout << "Time cost " << avg_time << " sec / frame \t " << avg_time * double(frame_n) / 3600.0 << " h / whole " << endl;		

			cout << endl;

			


			if(i%100==0){
				string score_record_name;
				score_record_name = test_reader->root + "recommendation_output/" 
					+ prefix_str + test_reader->video_name 
					+ "_" + my_setting.feature_code_string + "_score_record.bin";

				lc::LcMat2Bin( score_record_name.c_str() ,score_record);

				string corr_pos_name, corr_neg_name;

				corr_pos_name = test_reader->root + "recommendation_output/" 
					+ prefix_str + test_reader->video_name + "_"
					+ my_setting.feature_code_string + "_corr_pos.bin";
				corr_neg_name = test_reader->root + "recommendation_output/" 
					+ prefix_str + test_reader->video_name
					+ "_" + my_setting.feature_code_string + "_corr_neg.bin";

				lc::LcMat2Bin( corr_pos_name.c_str() ,corr_pos);
				lc::LcMat2Bin( corr_neg_name.c_str() ,corr_neg);
			}
		}	

		{
			string score_record_name;
			score_record_name = test_reader->root + "recommendation_output/" 
				+ prefix_str + test_reader->video_name 
				+ "_" + my_setting.feature_code_string + "_score_record.bin";

			lc::LcMat2Bin( score_record_name.c_str() ,score_record);

			string corr_pos_name, corr_neg_name;

			corr_pos_name = test_reader->root + "recommendation_output/" 
				+ prefix_str + test_reader->video_name 
				+ "_" + my_setting.feature_code_string + "_corr_pos.bin";
			corr_neg_name = test_reader->root + "recommendation_output/" 
				+ prefix_str + test_reader->video_name 
				+ "_" + my_setting.feature_code_string + "_corr_neg.bin";

			lc::LcMat2Bin( corr_pos_name.c_str() ,corr_pos);
			lc::LcMat2Bin( corr_neg_name.c_str() ,corr_neg);
		}
	}
	
	return 0;
}

void loadCrank(		LcInterface & my_setting,
					string name_str,
					vector< LcClassifier* > & baggings,
					string * p_video_name)
{
	LcVideoReadExt my_reader = my_setting.getTrainVideo();

	if( p_video_name != NULL)
		my_reader.video_name = *p_video_name;

	LocalSensitiveBagging crank_bagging;

	crank_bagging.setFeatureCode(my_setting.feature_code_string.c_str());

	crank_bagging.setReader( &my_reader );

	{
			string saving_name = my_reader.root + "classifier_output/crank/" + name_str;			

			crank_bagging.load( saving_name.c_str() );
	}

	baggings.resize( crank_bagging.h.size() );

	for(int i = 0 ; i < (int) baggings.size();i++)
	{
		baggings[i] = crank_bagging.h[i];
	}
}


LcClassifier * loadBagging(	LcInterface & my_setting,
						    string * p_video_name)
{
	LcVideoReadExt train_reader = my_setting.getTrainVideo();

	if( p_video_name != NULL)
		train_reader.video_name = *p_video_name;

	LcBagging * p_bagging = new LcBagging;
	LcBagging & bagging = *p_bagging;
	
	bagging.setClassifier( my_setting.getClassifier());
	bagging.setFeatureCode( my_setting.feature_code_string.c_str() );
	bagging.setReader( &train_reader );

	string saving_name = train_reader.root + "classifier_output/bagging/";

	bagging.load(saving_name.c_str());

	return &bagging;
}


void getLabelMask( Mat & res, Mat & mask , Size size, int bound_setting)
{
	int _rows = size.height;
	int _cols = size.width;

	int _sz = (_rows - 2* bound_setting) * (_cols - 2* bound_setting);

	if(_sz != (int) res.rows)
	{
		cout << "size unmatch while get label mask" << endl;
		return;
	}

	mask = Mat::zeros( _rows, _cols, CV_8U);

	vector<KeyPoint> keypts;

	{
		DenseFeatureDetector dfd;

		float initFeatureScale = 1.f;		// inital size
		int featureScaleLevels = 1;		// one level
		float featureScaleMul = 1.00f;	// multiplier (ignored if only one level)
		int train_initXyStep = 1;		// space between pixels for training (must be 1)

		dfd = DenseFeatureDetector(initFeatureScale,featureScaleLevels,featureScaleMul,train_initXyStep,bound_setting,true,false);

		dfd.detect(mask,keypts);
	}

	//cout << "kp size " << keypts.size() << " res size " << res.rows << endl;
	
	for(int i = 0;i< (int)keypts.size();i++)
	{
		int r = int(floor(keypts[i].pt.y+.5));
		int c = int(floor(keypts[i].pt.x+.5));
		mask.at<unsigned char>(r,c) = (unsigned char)min( 255.0f, res.at<float>(i,0) * 255.0f);
	}
}